`timescale 1ns/1ns
module apb_ram #(parameter SIZE_IN_BYTES = 1024) (
    input   wire            PCLK    ,
    input   wire            PRESETn ,
    input   wire    [31:0]  PADDR   ,
    input   wire            PSEL    ,
    input   wire            PENABLE ,
    input   wire            PWRITE  ,
    output  wire            PREADY  ,
    output  wire    [31:0]  PRDATA  ,
    input   wire    [31:0]  PWDATA   
);

reg [31:0] mem[0:SIZE_IN_BYTES/4-1];

wire read_setup = ~PWRITE & ~PENABLE & PSEL;
wire write_setup = PWRITE & ~PENABLE & PSEL;

reg ready = 1;
integer delay;
always @(posedge state[1], posedge state[2]) begin 
        if($random%2) begin 
        ready = 0;
        delay = 10 + $random%10;
        #delay;
        /*
        delay = 2 + $random%2;
        repeat(delay) @(posedge PCLK);
        */
        ready = 1;
    end
end

localparam  STATE_IDLE      = 4'b0001,
            STATE_READ      = 4'b0010,
            STATE_WRITE     = 4'b0100;

reg [3:0] state, state_next;
always @(posedge PCLK) begin 
    if(!PRESETn) begin 
        state <= STATE_IDLE;
    end
    else begin 
        state <= state_next;
    end
end

always @(*) begin 
    case(state) 
    STATE_IDLE : state_next = read_setup  ? STATE_READ  : 
                              write_setup ? STATE_WRITE : STATE_IDLE;
    STATE_READ : state_next = ready ? STATE_IDLE : STATE_READ;
    STATE_WRITE: state_next = ready ? STATE_IDLE : STATE_WRITE;
    default:     state_next = STATE_IDLE;
    endcase
end

always @(posedge PCLK) begin 
    if(state == STATE_WRITE && state_next == STATE_IDLE) begin 
        mem[PADDR] <= PWDATA;
    end
end

assign PRDATA = (state != STATE_READ) ? 32'b0 : 
                (!ready)              ? 32'bx : mem[PADDR];

assign PREADY = ready;
    
endmodule


module tb_apb_ram ();

parameter MEMSIZE_IN_BYTES = 1024;

reg clk;
initial begin 
    clk = 0;
    forever #5 clk = ~clk;
end

reg rstn;
initial begin 
    rstn = 0;
    #9 rstn = 1;
end

reg [31:0] addr;
reg [31:0] PWDATA;
wire [31:0] PRDATA;
reg [31:0] PADDR;
reg PWRITE, PSEL, PENABLE;
wire PREADY;

/*
task write(input [31:0] addr, input [31:0] data);
begin
    if(addr > MEMSIZE_IN_BYTES - 4) $stop();

    //@(posedge clk) 
    PADDR = addr;
    PWDATA = data;
    PWRITE = 1;
    PSEL = 1;
    PENABLE = 0;
    @(posedge clk)
    PENABLE = 1;

    begin : write_wait_for_ready
        while(1) begin 
            @(posedge clk)
            if(PREADY) disable write_wait_for_ready;
        end
    end
    PSEL = 0;
    PENABLE = 0;
end
endtask
*/

task write(input [31:0] addr, input [31:0] data);
begin
    if(addr > MEMSIZE_IN_BYTES - 4) $stop();

    //@(posedge clk) 
    PADDR <= addr;
    PWDATA <= data;
    PWRITE <= 1;
    PSEL <= 1;
    PENABLE <= 0;
    @(posedge clk)
    PENABLE <= 1;

    begin : write_wait_for_ready
        while(1) begin 
            @(posedge clk)
            if(PREADY) disable write_wait_for_ready;
        end
    end
    PSEL <= 0;
    PENABLE <= 0;
end
endtask

task read(input [31:0] addr); 
begin
    if(addr > MEMSIZE_IN_BYTES - 4) $stop();

    //@(posedge clk) 
    PADDR <= addr;
    PWRITE <= 0;
    PSEL <= 1;
    PENABLE <= 0;
    @(posedge clk)
    PENABLE <= 1;

    begin : read_wait_for_ready
        while(1) begin 
            @(posedge clk)
            if(PREADY) disable read_wait_for_ready;
        end
    end
    PSEL <= 0;
    PENABLE <= 0;
end
endtask

/*
initial begin 
    PADDR = 0;
    PWRITE = 0;
    PSEL = 0;
    PENABLE = 0;
    PWDATA = 0;
    #50;
    addr = 0;
    @(posedge clk);
    for(integer i=0; i<MEMSIZE_IN_BYTES/4; i=i+1) begin 
        write(addr, i);
        addr = addr + 1;
    end
    #50;
    addr = 0;
    @(posedge clk);
    for(integer i=0; i<MEMSIZE_IN_BYTES/4; i=i+1) begin 
        read(addr);
        addr = addr + 1;
    end
    #10 $finish();
end
*/

initial begin 
    begin 
        PADDR <= 0;
        PWRITE <= 0;
        PSEL <= 0;
        PENABLE <= 0;
        PWDATA <= 0;
    end
    #50;
    addr = 0;
    begin 
        @(posedge clk);
        for(integer i=0; i<MEMSIZE_IN_BYTES/4; i=i+1) begin 
            write(addr, i);
            addr = addr + 1;
        end
    end
    #50;
    addr = 0;
    begin 
        @(posedge clk);
        for(integer i=0; i<MEMSIZE_IN_BYTES/4; i=i+1) begin 
            read(addr);
            addr = addr + 1;
        end
    end
    #10 $finish();
end

apb_ram #(.SIZE_IN_BYTES(1024) ) I0 (
    .PCLK    (clk    ),
    .PRESETn (rstn   ),
    .PADDR   (PADDR  ),
    .PSEL    (PSEL   ),
    .PENABLE (PENABLE),
    .PWRITE  (PWRITE ),
    .PREADY  (PREADY ),
    .PRDATA  (PRDATA ),
    .PWDATA  (PWDATA ) 
);

initial begin 
    $dumpfile("dump1.vcd");
    $dumpvars(0, tb_apb_ram);
end


/*
reg [33:0] state1, state1_next;
localparam STATE1_WRITE = 2'b01,
           STATE1_READ  = 2'b10;

always @(posedge clk, negedge rstn) begin 
    if(!rstn) begin 
        state1 <= {STATE1_WRITE, 32'b0}
    end
    else begin 
        state1 <= state1_next;
    end
end

always @(*) begin 
    case(state1[33:32]) 

    endcase
end
*/

endmodule